/*
 *  Environnement.cpp
 *  Ray Tracing
 *
 *  Created by Jean-Marc Comby + Romain Dequesne on Fri May 14 2004.
 *  Copyright (c) 2004. All rights reserved.
 *
 *
 *  Environnement
 */

#include "Environnement.hpp"

#include "Bruit/perlin.hpp"

#include "Formes/Forme.hpp"

#include "Materiaux/Materiau.hpp"

#include <iostream>

#include <cmath>

using namespace std;

/**
 * Constructeur usuel d'un Environnement. Par défaut de fond est noir.
 *
 * @param col Couleur du found de l'environnement.
 */
Environnement::Environnement(const Couleur& col)
  : Fond( col )
{}

/**
 * Le destructeur n'a rien de spécial à faire.
 */
Environnement::~Environnement()
{}

/** Accessreur pour Fond.
 *
 * Permet d'accéder au membre Fond.
 *
 * @return une référence constante sur Fond.
 */
const Couleur& Environnement::fond() const
{
  return Fond;
}

/**
 * Permet de changer la couleur du fond de l'Environnement.
 */
void Environnement::setFond( const Couleur& col )
{
  Fond = col;
}

/**
 * Permet d'ajouter une Lumiere à la liste des sources lumineuses de
 * la scène.
 *
 * @param lampe pointeur sur la Lumiere à ajouter
 */
void Environnement::ajouterLumiere( Lumiere *lum )
{
  Lumieres.push_back( lum );
}

/**
 * Permet de supprimer la dernière source de lumière.
 */
void Environnement::supprimerDerniereLumiere()
{
  Lumieres.pop_back();
}

/** Supprime toutes les instances de Lumiere.
 */
void Environnement::supprimerLumieres()
{
  Lumieres.clear();
}

/**
 * Permet d'ajouter un Objet à la liste des objets de la scène.
 *
 * @param obj pointeur sur l'objet à ajouter.
 */
void Environnement::ajouterObjet( Objet *obj )
{
  Scene.push_back( obj );
}

/**
 * Permet de supprimer le dernier Objet.
 */
void Environnement::supprimerDernierObjet()
{
  Scene.pop_back();
}

/**
 * Permet de supprimer toutes les instances d'Objet.
 */
void Environnement::supprimerObjets()
{
  Scene.clear();
}

//------------------------------------
//methode qui gere le lancer de rayon
/**
 * Méthode qui fait le lancer de Rayon. Ici on recherche quel est
 * l'Objet qui est rencontré par le Rayon, on ajoute ensuite la
 * lumière diffusée par les différentes sources lumineuses. On calcule
 * aussi les rayons réfléchis (et éventuellement réfracté). Une fois
 * tous ces effets cumulés, on renvoie la Couleur. La méthode est
 * récursive (càd qu'un rayon est lancé vers chaque source, un rayon
 * réfléchi est lancé et un rayon réfracté si le Materiau est
 * transparent). Si aucune intersection n'est trouvée, la Couleur
 * renvoyée est la couleur de fond de l'Environnement.
 *
 * @param ray Rayon à lancer.
 * @param profondeur état dans la récursion (ne lance plus de rayon
 * supplémentaires lorsque profondeur = 0).
 * @param indice indice de réfraction ambiant.
 * @param dbg permet d'afficher certaines informations de debugging
 * (par défaut \e false).
 * @return la Couleur calculée pour le point d'intersection.
 */
Couleur Environnement::lanceRayon(const Rayon &ray,
				  unsigned const int& profondeur,
				  double indice, const bool& dbg)
{
  Couleur output(0,0,0); // couleur de sortie
  Coord inter(0,0,0); //coord d'intersection
  double delta(3.e-5);
  int nofig( trouvePremierObjet( ray, inter ) ); //la methode modifie aussi la coord du pt d'intersection
  //l'intersection est modifiee, on ne la recalcule plus
  Coord modifinter(inter); // intersection modifier pour eviter les points noirs sur les objets
  if ( nofig == -1 )
    {
      output+=Fond; //cas ou aucun objet est dans le chemin du rayon
    }
  else
    {
      if ( profondeur == 0 ) // permet de sortir de la recursion
	return output;
      double bruit=Scene[nofig]->habillage()->bruit();
      VecteurU vNormale=Scene[nofig]->volume()->normale(inter); // calcul du vecteur normal a l objet
      modifinter+=delta*vNormale; // pour eviter les points noirs, on avance un petit peu intersection
      if ( dbg )
	{
	  cout << "Intersection : " << inter << endl
	       << "Normale      : " << vNormale << endl
	       << "Mod inter    : " << modifinter << endl;
	}
      if ( bruit < 1.e-7 ) // on calcule sans modification du vecteur normal par Perlin
	{
	  output += calculeLumiereDiffusee(modifinter,inter,nofig,ray,vNormale, dbg);
	  if ( dbg )
	    cout << "couleur éclairée : " << output << endl;
	  if (Scene[nofig]->habillage()->reflexivite() > 1.e-7 )
	    {
	      output += calculeLumiereReflechie(modifinter,nofig,profondeur,ray,vNormale);
	      if ( dbg )
		cout << "couleur avec réflex : " << output << endl;
	    }
	  if (Scene[nofig]->habillage()->transparence() > 1.e-7 ) // on calcule seleument si c'est transparent
	    {
	      output += calculeLumiereRefractee(inter,nofig,ray,profondeur,indice,vNormale);
	       if ( dbg )
		cout<< "couleur avec réfrac : " << output << endl;
	    }
	  if ( dbg )
	    cout << "couleur finale : " << output << endl;
	}
      else // on modifie le vecteur avec le coef du Bruit
	{
	  // on perturbe notre vecteur normal grace a la fonction de perlin
	  double noiseCoefx = noise(bruit * inter.x(), bruit * inter.y(), bruit * inter.z());
	  double noiseCoefy = noise(bruit * inter.y(), bruit * inter.z(), bruit * inter.x());
	  double noiseCoefz = noise(bruit * inter.z(), bruit * inter.x(), bruit * inter.y());
	  // on obtient un vecteur normale perturbe
	  VecteurU vNormale_pert(vNormale.x()+bruit*noiseCoefx,vNormale.y()+bruit*noiseCoefy,vNormale.z()+bruit*noiseCoefz);
	  output+=calculeLumiereDiffusee(modifinter,inter,nofig,ray,vNormale_pert);
	  if ( Scene[nofig]->habillage()->reflexivite() > 1.e-7 )
	    {
	      output+=calculeLumiereReflechie(modifinter,nofig,profondeur,ray,vNormale_pert);
	    }
	  if (Scene[nofig]->habillage()->transparence()!=0.0) // on calcule seleument si c'est transparent
	    {
	      output+=calculeLumiereRefractee(inter,nofig,ray,profondeur,indice,vNormale_pert);
	    }
	}
    }
  if ( dbg )
    cout << "Couleur : " << output << endl;
  return output;
}

/**
 * Recherche le premier objet rencontré par le rayon. Permet de
 * récupérer quel est le premier objet ainsi que le point
 * d'intersection.
 *
 * @param ray Rayon.
 * @param a point d'intersection (écrit par la méthode).
 * @return le numéro de l'objet rencontré (position dans Scene), ou -1
 * si aucun objet n'est rencontré.
 */
int Environnement::trouvePremierObjet(const Rayon& ray, Coord& a)
{
  double pluspetitenorme(1e+30);
  Coord inter(0,0,0); //coord d intersection
  int nofig(-1); //numero de a figure la plus proche initialise a -1
  for(unsigned int i(0);i<Scene.size();i++) // on parcourt la scene et on cherche toute les intersections
    {
      // inter=Scene[i]->volume()->trouveIntersection(ray);
      // if (inter!=NullePart)
      if ( Scene[i] -> volume() -> aIntersection( ray ) )
	{
	  inter=Scene[i]->volume()->trouveIntersection(ray);
	  double dist(distance(ray.origine(),inter)); // distance de l'origine du rayon a l intersection
	  if(dist<pluspetitenorme) // on prend la plus courte distance
	    {
	      a=inter;
	      pluspetitenorme=dist;
	      nofig=i;
	    }
	}
    }
  return nofig;
}

//calcul de la lumiere diffuse
/**
 * Calcule la lumière diffuse. On lance un rayon vers chaque source
 * lumineuse. Si aucun objet opaque se trouve entre le point et la
 * source, on éclaire le point avec la source lumineuse (pondéré par
 * le cosinus de l'angle formé).
 *
 * @param intersection point de départ des rayons lancés. Correspond à
 * la vraie intersection, mais décalée de la surface, afin de ne pas
 * se trouver dans "l'ombre" de l'objet.
 * @param vintersection vraie valeur de l'intersection, nécessaire
 * pour obtenir la valeur de la couleur de l'objet.
 * @param nofig numéro de la figure, càd indice de l'objet dans Scene.
 * @param r Rayon incident sur le point.
 * @param normal direction de la normale à la figure au point
 * d'intersection.
 * @param dbg permet d'afficher certaines informations de debugging
 * (par défaut \e false).
 * @return la Couleur au point d'intersection, résultant de la Couleur
 * du point lui-même, éclairé par les différentes sources lumineuses.
 */
Couleur Environnement::calculeLumiereDiffusee(const Coord & intersection,
				       const Coord &vintersection,
				       unsigned const int& nofig,
				       const Rayon& r, const VecteurU &normal,
				       const bool& dbg)
{
  //cerr << "Intersection : " << intersection << endl;
  Couleur retval( 0, 0, 0 );
  for (unsigned int i(0), j; i<Lumieres.size();i++)
    {
      Rayon ray(intersection, Lumieres[i]->position()); // rayon qui part de l objet jusqu a la source lumineuse
      bool danslombre = false; // va permettre de determiner si le nouveau rayon est dans l ombre d un autre objet
      double coef_transparence(0.0); // stock le coeficient de transparence des objets traverses
      int compteur=0; // va permettre de ponderer le coef de transparence si plusieurs objets transparents sont traverses
      bool objettransparent = false; // indique si un rayon est passe a travers un objet transparent
      Coord inter;
      if ( Lumieres[i]->position() != NullePart )
	{
	  for(j = 0; j < Scene.size(); j++) // on passe tout les objets au rayon
	    {
	      if ( ! ( Scene[ j ] -> volume() -> aIntersection( ray ) ) )
		{
		  inter = NullePart;
		}
	      else
		{
		  inter=Scene[j]->volume()->trouveIntersection(ray); // si il y a une intersection le point est dans l'ombre d un objet
		}
	      if (inter!=NullePart) // sauf si celui ci est transparent
		{
		  objettransparent=Scene[j]->habillage()->estTransparent();
		  coef_transparence+=Scene[j]->habillage()->transparence();
		  compteur++;
		  if (!objettransparent) // si l'objet est transparent on est pas dans l ombre
		    {
		      if ( dbg ) cout << "Dans l'ombre de : " << Scene[j]->volume()->nom() << endl;
		      danslombre=true;
		      break; // sort tout de suite, pas besoin de chercher un autre objet
		    }
		}
	    }

	  if (!danslombre) // si le point n'est pas dans l'ombre d'un objet
	    {
	      if (objettransparent) // si on est passe dans un objet transparent
		{
		  double coef_transp_pondere=coef_transparence / compteur; // on attenue la lumiere par le coef pondere de la transparence
		  Couleur tmp=Scene[nofig]->habillage()->lumiereDiffusee(Lumieres[i]->color(), normal.angle(ray.direction()),vintersection);
		  if ( dbg ) cout << "Attenuation transp : " << coef_transp_pondere << endl;
		  tmp.attenue(coef_transp_pondere);
		  tmp.attenue( Lumieres[i] -> attenuation( intersection ) );
		  if ( dbg ) cout << "Attenuation dist : " <<  Lumieres[i] -> attenuation( intersection ) << endl;
		  retval+=tmp;
		}
	      else
		{
		  if ( dbg ) cout << "Couleur lumière : " << Lumieres[i]->color() << endl;
		  Couleur tmp = Scene[nofig]->habillage()->lumiereDiffusee(Lumieres[i]->color(), normal.angle(ray.direction()),vintersection);
		  if ( dbg ) cout << "Couleur avant : " << tmp << endl;
		  tmp.attenue( Lumieres[i] -> attenuation( intersection ) );
		  if ( dbg ) cout << "Attenuation : " <<  Lumieres[i] -> attenuation( intersection ) << endl;
		  if ( dbg ) cout << "Couleur après : " << tmp << endl;
		  retval+=tmp;
		}
	    }
	}
      else
	{
	  if ( dbg ) cout << "Couleur lumière : " << Lumieres[i]->color() << endl;
	  Couleur tmp = Scene[nofig]->habillage()->lumiereDiffusee(Lumieres[i]->color(), 0., vintersection );
	  if ( dbg ) cout << "Couleur avant : " << tmp << endl;
	  tmp.attenue( Lumieres[i] -> attenuation( intersection ) );
	  if ( dbg ) cout << "Attenuation amb : " <<  Lumieres[i] -> attenuation( intersection ) << endl;
	  if ( dbg ) cout << "Couleur après : " << tmp << endl;
	  retval+=tmp;
	}
    }
  if ( dbg ) cout << "Couleur finale : " << retval << endl;
  return retval;
}


//---------------------------

//calcul de la lumiere reflechie - ne fonctionne pas encore, ou disons plutot pas teste franchement
/**
 * Calcule la lumière réfléchie par un objet. On relance un Rayon qui
 * est obtenu par réflexion du Rayon incident.
 *
 * La Couleur obtenue est ensuite pondérée par le coefficient de
 * reflexion du Materiau.
 *
 * @param intersection point de la surface réfléchissante.
 * @param nofig index de l'Objet dans Scene.
 * @param profondeur nombre d'appel récursif encore possible.
 * @param r Rayon incident.
 * @param normal vecteur normal à la surface.
 * @return la couleur renvoyée par le Rayon réfléchi ou noir si aucune
 * intersection n'est trouvée.
 */
Couleur Environnement::calculeLumiereReflechie(const Coord & intersection,
				 unsigned const int& nofig,
				 const int & profondeur,
				 const Rayon &r, const VecteurU &normal)
{
  Couleur retval;
  VecteurU vReflect( - r.direction().reflect( normal ) );
  Rayon ray( intersection, vReflect );
  Couleur tmp( lanceRayon( ray, profondeur-1, 1. ) );
  if ( tmp == Fond )
    return retval;
  return Scene[nofig]->habillage()->lumiereReflechie(tmp);
}


//---------------------------

//calcul de la lumiere refractee
/**
 * Calcule la lumière réfractée par un objet. Un nouveau Rayon est
 * lancé, dont l'angle avec la normale est déterminé par la loi de
 * Snell-Descartes.
 *
 * @param a point d'intersection avec la surface de l'objet, ce point
 * est avancé de \f$10^{-7}\f$ dans la direction de la normale.
 * @param nofig indice de l'Objet dans Scene.
 * @param r Rayon incident.
 * @param profondeur nombre d'appel récursif encore possible.
 * @param indice1 indice de réfraction de l'air.
 * @param normal vecteur normal à la surface.
 * @return la couleur donnée par le lancer du Rayon réfracté, ou noir
 * sinon.
 */
Couleur Environnement::calculeLumiereRefractee(Coord a, unsigned const int& nofig,
				  const Rayon & r,
				  unsigned const int& profondeur,
				  const double &indice1,
				  const VecteurU &normal)
{
  double indice2; // indice du milieu sortant
  double delta( 3.e-5 ); // va permettre d'eviter les points louches
  Coord vintersection( a ); //vrai intersection pas modifie
  double cos_ar;
  if (Scene[nofig]->volume()->estDans(r.origine())) // si l'origine du rayon est dans la l'objet, il faut inverser les coefficients
    {
      indice2 = 1.; // on retourne l'indice de l'air
      a += delta * normal; // on avance le point d intersection hors de l objet
      cos_ar = r.direction() * normal; //angle entre a et r
    }
  else
    {
      indice2 = Scene[ nofig ] -> habillage() -> indice(); // sinon celui de l'objet
      a -= delta * normal; // on avance le point d'intersection dans l'objet
      cos_ar= - r.direction() * normal; //angle entre a et r
    }
  Couleur output;
  double q( indice1 / indice2 ); //quotien cf note du cours-projet
  double check( 1 - q * q * ( 1. - cos_ar * cos_ar ) ); // on verifie si c est plus petit que 0
  if ( check < -1.e-7 )
    return output;
  double cos_at( sqrt( check ) );
  VecteurU direction( ( q * cos_ar - cos_at ) * normal + q * r.direction() );
  Rayon ray( a, direction ); // nouveau rayon qui part de l interieur
  Couleur tmp( lanceRayon(ray,profondeur-1,indice2) );
  if ( tmp == Fond )
    return output;
  output += Scene[ nofig ] -> habillage() -> lumiereRefractee( tmp, vintersection );
  return output;
}

/**
 * Affiche un petit récapitulatif des instances d'Objet présentes dans
 * la scène et des instances de Lumière qui l'éclaire. L'affichage se
 * fait dans la sortie standard.
 */
void Environnement::affiche() const
{
  vector< Lumiere* >::const_iterator srcIt, srcDebut, srcFin;
  srcDebut = Lumieres.begin();
  srcFin   = Lumieres.end();

  for (srcIt = srcDebut; srcIt != srcFin; srcIt++)
    {
      cerr << "Lumière à la position : ";
      cerr << (*srcIt) -> position();
      cerr << ", couleur : ";
      cerr << (*srcIt) -> color() << endl;
    }

  vector< Objet* >::const_iterator objIt, objDebut, objFin;
  objDebut = Scene.begin();
  objFin   = Scene.end();

  for (objIt = objDebut; objIt != objFin; objIt++)
    (*objIt) -> volume() -> affiche( cout );
}

//methode super privee
/**
 * Permet de calculer la distance entre deux points.
 *
 * On définit un Vecteur à partir des deux point et on calcule sa
 * norme.
 *
 * @param a un point de l'espace. 
 * @param b un point de l'espace.
 * @return le carré de la norme du Vecteur définit par a et b.
 */
double Environnement::distance(const Coord& a, const Coord& b)
{
  Vecteur ab(a,b);
  return ab.norme();
}
